/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package aggr

import com.google.protobuf.CodedOutputStream
import com.google.protobuf.CodedInputStream
import com.google.protobuf.ByteString
import com.google.protobuf.Message
import com.google.protobuf.DynamicMessage
import CodedOutputStream._

import SerializedTypes._

/**
 * Module for converter internal expression to ProtocolBuffers byte string format.
 */
object Serializer {
  
  private def os(size: Int, f: (CodedOutputStream => Unit)) = {
    val bs = ByteString.newOutput()
    val cos = CodedOutputStream.newInstance(bs)
    f(cos)
    cos.flush()
    bs.toByteString
  }

  def serializeBool(x: Boolean): ByteString  = os(computeBoolSizeNoTag(x), _.writeBoolNoTag(x))
  def serializeDouble(x: Double): ByteString = os(computeDoubleSizeNoTag(x), _.writeDoubleNoTag(x))
  def serializeEnum(x: Int): ByteString      = os(computeEnumSizeNoTag(x), _.writeEnumNoTag(x))
  def serializeFixed32(x: Int): ByteString   = os(computeFixed32SizeNoTag(x), _.writeFixed32NoTag(x))
  def serializeFixed64(x: Long): ByteString  = os(computeFixed64SizeNoTag(x), _.writeFixed64NoTag(x))
  def serializeSFixed32(x: Int): ByteString  = os(computeSFixed32SizeNoTag(x), _.writeSFixed32NoTag(x))
  def serializeSFixed64(x: Long): ByteString = os(computeSFixed64SizeNoTag(x), _.writeSFixed64NoTag(x))
  def serializeFloat(x: Float): ByteString   = os(computeFloatSizeNoTag(x), _.writeFloatNoTag(x))
  def serializeInt32(x: Int): ByteString     = os(computeInt32SizeNoTag(x), _.writeInt32NoTag(x))
  def serializeInt64(x: Long): ByteString    = os(computeInt64SizeNoTag(x), _.writeInt64NoTag(x))
  def serializeSInt32(x: Int): ByteString    = os(computeSInt32SizeNoTag(x), _.writeSInt32NoTag(x))
  def serializeSInt64(x: Long): ByteString   = os(computeSInt64SizeNoTag(x), _.writeSInt64NoTag(x))
  def serializeUInt32(x: Int): ByteString    = os(computeUInt32SizeNoTag(x), _.writeUInt32NoTag(x))
  def serializeUInt64(x: Long): ByteString   = os(computeUInt64SizeNoTag(x), _.writeUInt64NoTag(x))

  /**
   * Serialize data.
   */
  def serialize(dataType: SerializedType, d: Any): ByteString = {
    (dataType, d) match {
      case (MessageType(m), d: Message)    => d.toByteString()
      case (EnumType(e),    d: Int)        => serializeEnum(d)
      case (BoolType,       d: Boolean)    => serializeBool(d)
      case (FloatType,      d: Float)      => serializeFloat(d)
      case (DoubleType,     d: Double)     => serializeDouble(d)
      case (Fixed32Type,    d: Int)        => serializeFixed32(d)
      case (Fixed64Type,    d: Long)       => serializeFixed64(d)
      case (SFixed32Type,   d: Int)        => serializeSFixed32(d)
      case (SFixed64Type,   d: Long)       => serializeSFixed64(d)
      case (Int32Type,      d: Int)        => serializeInt32(d)
      case (Int64Type,      d: Long)       => serializeInt64(d)
      case (SInt32Type,     d: Int)        => serializeSInt32(d)
      case (SInt64Type,     d: Long)       => serializeSInt64(d)
      case (UInt32Type,     d: Int)        => serializeUInt32(d)
      case (UInt64Type,     d: Long)       => serializeUInt64(d)
      case (StringType,     d: String)     => ByteString.copyFromUtf8(d)
      case (BytesType,      d: ByteString) => d
    }
  }
}

/**
 * Module for converter ProtocolBuffers byte string format to internal expression.
 */
object Deserializer {

  private def is(b: ByteString) = b.newCodedInput()

  def deserializeBool(x: ByteString): Boolean = is(x).readBool()
  def deserializeDouble(x: ByteString): Double= is(x).readDouble()
  def deserializeEnum(x: ByteString): Int = is(x).readEnum()
  def deserializeFixed32(x: ByteString): Int = is(x).readFixed32()
  def deserializeFixed64(x: ByteString): Long = is(x).readFixed64()
  def deserializeSFixed32(x: ByteString): Int = is(x).readSFixed32()
  def deserializeSFixed64(x: ByteString): Long = is(x).readSFixed64()
  def deserializeFloat(x: ByteString): Float= is(x).readFloat()
  def deserializeInt32(x: ByteString): Int= is(x).readInt32()
  def deserializeInt64(x: ByteString): Long = is(x).readInt64()
  def deserializeSInt32(x: ByteString): Int= is(x).readSInt32()
  def deserializeSInt64(x: ByteString): Long = is(x).readSInt64()
  def deserializeUInt32(x: ByteString): Int= is(x).readUInt32()
  def deserializeUInt64(x: ByteString): Long = is(x).readUInt64()

  /**
   * Deserialize data.
   */
  def deserialize(dataType: SerializedType, b: ByteString): Any = {
    dataType match {
      case MessageType(m) => DynamicMessage.parseFrom(m, b)
      case EnumType(e)    => e.findValueByNumber(deserializeEnum(b))
      case BoolType       => deserializeBool(b)
      case FloatType      => deserializeFloat(b)
      case DoubleType     => deserializeDouble(b)
      case Fixed32Type    => deserializeFixed32(b)
      case Fixed64Type    => deserializeFixed64(b)
      case SFixed32Type   => deserializeSFixed32(b)
      case SFixed64Type   => deserializeSFixed64(b)
      case Int32Type      => deserializeInt32(b)
      case Int64Type      => deserializeInt64(b)
      case SInt32Type     => deserializeSInt32(b)
      case SInt64Type     => deserializeSInt64(b)
      case UInt32Type     => deserializeUInt32(b)
      case UInt64Type     => deserializeUInt64(b)
      case StringType     => b.toString("UTF-8")
      case BytesType      => b
    }
  }
}
